/*
Copyright SecureKey Technologies Inc. All Rights Reserved.

SPDX-License-Identifier: Apache-2.0
*/
package chpvdr

import (
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/discovery"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/common/options"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/chconfig"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/events/deliverclient"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/ichain/membership"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/providers/ichain"
	"git.cloud.inspur.com/ichain/ichain-sdk-go/pkg/util/concurrent/lazycache"
	"github.com/pkg/errors"
)

type cache interface {
	Get(lazycache.Key, ...interface{}) (interface{}, error)
	Close()
}

type contextCache struct {
	ctx                   ichain.ClientContext
	eventServiceCache     cache
	discoveryServiceCache cache
	chCfgCache            cache
	membershipCache       cache
}

var cfgCacheProvider = func(opts ...options.Opt) cache {
	return chconfig.NewRefCache(opts...)
}

func newContextCache(ctx ichain.ClientContext, opts []options.Opt) *contextCache {
	eventIdleTime := ctx.EndpointConfig().Timeout(ichain.EventServiceIdle)
	chConfigRefresh := ctx.EndpointConfig().Timeout(ichain.ChainConfigRefresh)
	membershipRefresh := ctx.EndpointConfig().Timeout(ichain.ChainMembershipRefresh)

	c := &contextCache{
		ctx: ctx,
	}

	c.chCfgCache = cfgCacheProvider(append(opts, chconfig.WithRefreshInterval(chConfigRefresh))...)

	// 避免重复请求链配置
	c.membershipCache = membership.NewRefCache(membershipRefresh)

	// 服务发现缓存初始化, todo 目前是静态服务发现，缓存意义不大
	c.discoveryServiceCache = lazycache.New(
		"Discovery_Service_Cache",
		func(key lazycache.Key) (interface{}, error) {
			ck := key.(*cacheKey)
			return c.createDiscoveryService(ck.chainConfig, opts...)
		},
	)

	// 事件缓存，避免重复连接
	c.eventServiceCache = lazycache.New(
		"Event_Service_Cache",
		func(key lazycache.Key) (interface{}, error) {
			ck := key.(*eventCacheKey)
			return NewEventClientRef(
				eventIdleTime,
				func() (ichain.EventClient, error) {
					return c.createEventClient(ck.chainConfig, ck.opts...)
				},
			), nil
		},
	)

	return c
}

func (c *contextCache) Close() {
	logger.Debug("Closing event service cache...")
	c.eventServiceCache.Close()

	logger.Debug("Closing membership cache...")
	c.membershipCache.Close()

	logger.Debug("Closing chain configuration cache...")
	c.chCfgCache.Close()

	logger.Debug("Closing discovery service cache...")
	c.discoveryServiceCache.Close()
}

func (c *contextCache) createEventClient(chConfig ichain.ChainCfg, opts ...options.Opt) (ichain.EventClient, error) {
	disc, err := c.GetDiscoveryService(chConfig.ID())
	if err != nil {
		return nil, errors.WithMessage(err, "could not get discovery service")
	}

	logger.Debugf("Using deliver events for chain [%s]", chConfig.ID())
	return deliverclient.New(c.ctx, chConfig, disc, opts...)
}

func (c *contextCache) createDiscoveryService(chConfig ichain.ChainCfg, opts ...options.Opt) (ichain.DiscoveryService, error) {
	// todo 新版本之后需要根据版本来创建是否支持动态获取节点
	if chConfig.HasCapability(ichain.ApplicationGroupKey, ichain.V1_0Capability) {
	}
	return discovery.NewStaticDiscovery(c.ctx.EndpointConfig(), c.ctx.IChainProvider(), chConfig.ID())
}

func (c *contextCache) GetDiscoveryService(chainID string) (ichain.DiscoveryService, error) {
	chnlCfg, err := c.GetChainConfig(chainID)
	if err != nil {
		return nil, err
	}
	key := newCacheKey(chnlCfg)
	discoveryService, err := c.discoveryServiceCache.Get(key)
	if err != nil {
		return nil, err
	}
	return discoveryService.(ichain.DiscoveryService), nil
}

func (c *contextCache) GetEventService(chainID string, opts ...options.Opt) (ichain.EventService, error) {
	chCfg, err := c.GetChainConfig(chainID)
	if err != nil {
		return nil, err
	}
	key, err := newEventCacheKey(chCfg, opts...)
	if err != nil {
		return nil, err
	}
	eventService, err := c.eventServiceCache.Get(key)
	if err != nil {
		return nil, err
	}

	return eventService.(ichain.EventService), nil
}

// GetChainConfig 在缓存中获取链配置
// @param chainID
// @return ichain.ChainCfg
// @return error
func (c *contextCache) GetChainConfig(chainID string) (ichain.ChainCfg, error) {
	if chainID == "" {
		// System chain
		return chconfig.NewChainCfg(""), nil
	}
	chCfgRef, err := c.loadChainCfgRef(chainID)
	if err != nil {
		return nil, err
	}
	chCfg, err := chCfgRef.Get()
	if err != nil {
		return nil, errors.WithMessage(err, "could not get chConfig cache reference")
	}
	return chCfg.(ichain.ChainCfg), nil
}

func (c *contextCache) loadChainCfgRef(chainID string) (*chconfig.Ref, error) {
	// 先在缓存中获取 缓存key为chainID
	key, err := chconfig.NewCacheKey(c.ctx, func(string) (ichain.ChainConfigService, error) { return chconfig.NewChainConfigService(chainID) }, chainID)
	if err != nil {
		return nil, err
	}
	cfg, err := c.chCfgCache.Get(key)
	if err != nil {
		return nil, err
	}

	return cfg.(*chconfig.Ref), nil
}

func (c *contextCache) GetMembership(chainID string) (ichain.ChainMembership, error) {
	chCfgRef, err := c.loadChainCfgRef(chainID)
	if err != nil {
		return nil, err
	}
	key, err := membership.NewCacheKey(membership.Context{Providers: c.ctx, EndpointConfig: c.ctx.EndpointConfig()},
		chCfgRef.Reference, chainID)
	if err != nil {
		return nil, err
	}
	ref, err := c.membershipCache.Get(key)
	if err != nil {
		return nil, err
	}

	return ref.(*membership.Ref), nil
}
